package com.nchu.ft.common.storage.impl;


import com.nchu.ft.common.exception.file.FileNotExistException;
import com.nchu.ft.common.exception.file.NullStorageConfigurationException;
import com.nchu.ft.common.storage.StorageStrategy;
import com.nchu.ft.common.storage.config.MinioProperties;
import io.micrometer.common.util.StringUtils;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Item;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

/**
 * @ClassName: MinioStorageImpl
 * @description:
 * @Author: Chuanwei Li
 * @CreateTime: 2025-04-28 13:33
 * @Version: 1.0
 */

@Component
@Slf4j
public class MinioStorageImpl implements StorageStrategy {
    @Autowired
    private MinioProperties minioProperties;

    /**
     * 私有构造方法，防止外部实例化
     */
    private MinioStorageImpl() {}

    /**
     * 添加私有静态成员变量来保存单例实例
     */
    private static MinioClient minioClientInstance;

    /**
     * 通过这个方法获取实例，使用懒汉模式
     */
    private static synchronized MinioClient getOSSClientInstance(String url, String username, String password, String bucketName) {
        if (minioClientInstance == null) {
            minioClientInstance = buildOSSClient(url, username, password, bucketName);
        }
        return minioClientInstance;
    }

    private static MinioClient buildOSSClient(String url, String username, String password, String bucketName){
        //判断配置是否为空
        if(!checkConfig(url, username, password, bucketName)){
            throw new NullStorageConfigurationException("minio的url, username, password, bucketName");
        }
        MinioClient minioClient = MinioClient.builder().endpoint(url)
                .credentials(username, password).build();
        if (!bucketExists(minioClient, bucketName)) {
            log.info("Creating bucket " + bucketName);
            createBucket(minioClient, bucketName);
        }
        return minioClient;
    }

    /**
     * 检测配置是否为空
     *
     */
    public static boolean checkConfig(String url, String username, String password, String bucketName) {
        if(StringUtils.isBlank(url) || StringUtils.isBlank(username) || StringUtils.isBlank(password) || StringUtils.isBlank(bucketName)) {
            return false;
        }
        return true;
    }

    /**
     * 生成已上传文件的URL
     *
     */
    private String getObjectUrl(MinioClient minioClient, String bucketName, String objectName) {
        return null;
    }

    /**
     * 判断bucket是否存在
     *
     */
    private static boolean bucketExists(MinioClient minioClient, String bucketName) {
        try {
            return minioClient.bucketExists(BucketExistsArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            log.error("查看bucket是否存在", e);
            return false;
        }
    }

    /**
     * 创建新的bucket
     *
     */
    private static void createBucket(MinioClient minioClient, String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            log.error("创建桶失败", e);
        }
    }

    /**
     * 创建文件夹或目录
     *
     * @param directoryName 目录路径
     * @return true:成功 false:失败
     */
    @Override
    public boolean createDirectory(String directoryName) {
        MinioClient minioClient = getOSSClientInstance(minioProperties.getUrl(), minioProperties.getUsername(), minioProperties.getPassword(), minioProperties.getBucketName());
        try {
            // Specify bucket name and object name (directory path)
            String bucketName = minioProperties.getBucketName();
            String content = "";

            // Check if the directory name ends with "/"
            if (!directoryName.endsWith("/")) {
                directoryName += "/";
            }

            // Create PutObjectArgs object
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(directoryName)
                    .stream(new ByteArrayInputStream(content.getBytes()), content.length(), -1)
                    .build();

            // Upload an empty string as an object to create the directory
            minioClient.putObject(putObjectArgs);

            log.info("Directory '{}' created successfully", directoryName);
            return true;
        } catch (Exception e) {
            log.error("Failed to create directory '{}'", directoryName, e);
            return false;
        }
    }

    /**
     * 判断文件是否存在
     *
     * @param objectName 对象
     * @return 存在返回true，不存在发生异常返回false
     */
    @Override
    public boolean objectExist(String objectName) {
        MinioClient minioClient = getOSSClientInstance(minioProperties.getUrl(), minioProperties.getUsername(), minioProperties.getPassword(), minioProperties.getBucketName());
        try {
            // Specify bucket name and object name
            String bucketName = minioProperties.getBucketName();

            // Check if the object exists
            StatObjectResponse statObjectResponse = minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build());

            return statObjectResponse != null;
        } catch (Exception e) {
            log.error("Failed to check if object '{}' exists", objectName, e);
            return false;
        }
    }

    /**
     * 判断文件夹是否存在【注意是文件夹而不是目录】
     *
     * @param folderName 文件夹名称（去掉前后的/）
     * @return 存在返回true，不存在发生异常返回false
     */
    @Override
    public boolean folderExist(String folderName) {
        MinioClient minioClient = getOSSClientInstance(minioProperties.getUrl(), minioProperties.getUsername(), minioProperties.getPassword(), minioProperties.getBucketName());
        try{
            String bucketName = minioProperties.getBucketName();
            // List objects in the bucket with the specified prefix (folderPath)
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .prefix(folderName)
                            .recursive(false)
                            .build());

            // Check if there is at least one object in the result
            return results.iterator().hasNext();
        }
        catch (Exception e){
            log.error("Failed to check if folder '{}' exists", folderName, e);
        }
        return false;
    }

    /**
     * 文件上传
     *
     * @param multipartFile 待上传文件
     * @param folderName    目录
     * @param aimFileName   最终保存到存储中的文件名，不需要后缀
     * @return 返回上传成功后的文件的url
     */
    @Override
    public String uploadFileAndGetURL(MultipartFile multipartFile, String folderName, String aimFileName) {
        MinioClient minioClient = getOSSClientInstance(minioProperties.getUrl(), minioProperties.getUsername(), minioProperties.getPassword(), minioProperties.getBucketName());

        try {
            // Specify bucket name
            String bucketName = minioProperties.getBucketName();

            // Check if the folder name ends with "/"
            if (folderName != null && !folderName.endsWith("/")) {
                folderName += "/";
            }

            // Check if the aimFileName is provided, otherwise generate a UUID
            if (!org.springframework.util.StringUtils.hasText(aimFileName)) {
                aimFileName = UUID.randomUUID().toString();
            }

            // Get the file extension
            String originalFilename = multipartFile.getOriginalFilename();

            // Add the file extension to the aimFileName
            if (originalFilename != null && originalFilename.lastIndexOf(".") != -1) {
                String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
                aimFileName += suffix;
            }

            // Full object name with path
            String objectName = org.springframework.util.StringUtils.hasText(folderName) ? folderName + aimFileName : aimFileName;

            // Create PutObjectRequest object
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(multipartFile.getInputStream(), multipartFile.getSize(), -1)
                    .contentType(multipartFile.getContentType())
                    .build();

            // Upload the file to MinIO
            minioClient.putObject(putObjectArgs);

            // Generate and return the URL for the uploaded file
            // URL expiry time in seconds (1 hour in this case)
            return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(bucketName)
                    .object(objectName)
                    .expiry(60 * 60)
                    .build());
        } catch (Exception e) {
            log.error("File upload failed", e);
            return null;
        }
    }

    /**
     * 上传文件【不指定文件夹】
     *
     * @param multipartFile 待上传文件
     * @param fileName      最终保存到存储中的文件名，不需要后缀
     * @return 返回上传成功后的文件的url
     */
    @Override
    public String uploadFileAndGetURL(MultipartFile multipartFile, String fileName) {
        return uploadFileAndGetURL(multipartFile,null,fileName);
    }

    /**
     * 上传文件【不指定文件夹,不指定目标文件名】
     *
     * @param multipartFile 待上传文件
     * @return 返回上传成功后的文件的url
     */
    @Override
    public String uploadFileAndGetURL(MultipartFile multipartFile) {
        return uploadFileAndGetURL(multipartFile,null,null);
    }

    /**
     * 拷贝文件
     *
     * @param destinationBucketName 目标bucket名称
     * @param destinationKey        目标文件名称
     * @param sourceKey             文件名称（包括目录）
     * @return 成功返回true，失败发生异常返回false
     */
    @Override
    public boolean copyObject(String destinationBucketName, String destinationKey, String sourceKey) {
        MinioClient minioClient = getOSSClientInstance(minioProperties.getUrl(), minioProperties.getUsername(), minioProperties.getPassword(), minioProperties.getBucketName());

        try {
            // Specify source and destination buckets
            String sourceBucketName = minioProperties.getBucketName();

            // Create CopyObjectArgs object
            CopyObjectArgs copyObjectArgs = CopyObjectArgs.builder()
                    .source(CopySource.builder().bucket(sourceBucketName).object(sourceKey).build())
                    .bucket(destinationBucketName)
                    .object(destinationKey)
                    .build();

            // Copy the object
            minioClient.copyObject(copyObjectArgs);

            log.info("Object '{}' copied to '{}' successfully", sourceKey, destinationKey);
            return true;
        } catch (Exception e) {
            log.error("Copy object failed", e);
            return false;
        }
    }

    /**
     * 文件下载
     *
     * @param fileName 文件名称
     * @param response response
     * @return 成功返回true，失败发生异常返回false
     */
    @Override
    public boolean getObject(String fileName, HttpServletResponse response) {
        MinioClient minioClient = getOSSClientInstance(minioProperties.getUrl(), minioProperties.getUsername(), minioProperties.getPassword(), minioProperties.getBucketName());
        if(objectExist(fileName)){
            try {
                // Specify bucket name
                String bucketName = minioProperties.getBucketName();

                // Create GetObjectArgs object
                GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .build();

                // Get the object data
                InputStream inputStream = minioClient.getObject(getObjectArgs);

                // Set response headers
                response.setCharacterEncoding("utf-8");
                response.setContentType("application/octet-stream");
                response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));

                // Get output stream from the response
                OutputStream outputStream = response.getOutputStream();

                // Copy file data to the response output stream
                byte[] buffer = new byte[1024];
                int len;
                while ((len = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, len);
                }

                // Close input stream and flush/close output stream
                inputStream.close();
                outputStream.flush();
                outputStream.close();

                log.info("'{}' file downloaded successfully", fileName);
                return true;
            } catch (Exception e) {
                log.error("File download failed", e);
                return false;
            }
        }
        else {
            throw new FileNotExistException(fileName);
        }
    }

    /**
     * 获取指定路径下的所有文件对象
     *
     * @param keyPrefix 文件名称
     * @return 存储bucket内指定路径下文件对象信息
     */
    @Override
    public List<String> getBucketObjects(String keyPrefix) {
        MinioClient minioClient = getOSSClientInstance(minioProperties.getUrl(), minioProperties.getUsername(), minioProperties.getPassword(), minioProperties.getBucketName());

        List<String> objectNames = new ArrayList<>();

        try {
            // Specify bucket name
            String bucketName = minioProperties.getBucketName();
            // Set to true to list only immediate objects
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .prefix(keyPrefix)
                            .recursive(true)
                            .build());

            Iterator<Result<Item>> iterator = results.iterator();
            while (iterator.hasNext()) {
                Item item = iterator.next().get();
                objectNames.add(item.objectName());
            }
        } catch (Exception e) {
            log.error("Failed to list objects in Minio bucket", e);
        }

        return objectNames;
    }

    /**
     * 删除文件
     *
     * @param fileName 文件名
     * @return 成功返回true，失败发生异常返回false
     */
    @Override
    public boolean removeObject(String fileName) {
        MinioClient minioClient = getOSSClientInstance(minioProperties.getUrl(), minioProperties.getUsername(), minioProperties.getPassword(), minioProperties.getBucketName());
        if(objectExist(fileName)){
            try {
                // Specify bucket name
                String bucketName = minioProperties.getBucketName();

                minioClient.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(bucketName)
                                .object(fileName)
                                .build());

                log.info("'{}'文件删除成功", fileName);
                return true;
            } catch (Exception e) {
                log.error("Failed to delete file from Minio bucket", e);
                return false;
            }
        }
        else {
            throw new FileNotExistException(fileName);
        }
    }
}
